package org.fusionide.builder.parsers.cfml;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.fusionide.builder.parsers.IParserHandler;
import org.fusionide.builder.parsers.IRootParserContext;
import org.fusionide.builder.parsers.ParserException;
import org.fusionide.util.RegEx;

public abstract class CFMLTag implements IRootParserContext {

	private static final String attributePattern = "\\s*=\\s*\"(.*?)\"";

	protected Vector<CFMLTag> children = new Vector<CFMLTag>();

	protected CFMLTag parent;

	protected int startOffset;

	protected int endOffset;

	protected int startLine;

	protected int endLine;

	protected String attributes;

	public CFMLTag(String attributes, int startOffset) {
		this.attributes = attributes;
		this.startOffset = startOffset;
	}

	public static Vector<Variable> collapseVariableScope(Vector<Variable> variableScope) {
		Vector<Variable> vars = new Vector<Variable>();
		Hashtable<String, Variable> varMap = new Hashtable<String, Variable>();

		// Collapse vars into variable map. If an old variable is var scoped,
		// copy it to the new one
		Enumeration<Variable> varEnum = variableScope.elements();
		while (varEnum.hasMoreElements()) {
			Variable var = varEnum.nextElement();
			Variable oldVar = varMap.put(var.getName().toLowerCase(), var);
			if (oldVar != null && oldVar.isVarScoped())
				var.setVarScoped(true);
		}

		// Enumerate the variable map and set any variables that use the current
		// variable as a scope to it's var scopedness
		varEnum = varMap.elements();

		while (varEnum.hasMoreElements()) {
			Variable var = varEnum.nextElement();
			Enumeration<Variable> varScopeMap = varMap.elements();
			while (varScopeMap.hasMoreElements()) {
				Variable varScope = varScopeMap.nextElement();
				if (varScope.getScope().toLowerCase().startsWith(var.getName().toLowerCase() + ".") && var.isVarScoped()) {
					varScope.setVarScoped(true);
				}
			}
		}

		vars.addAll(varMap.values());
		return vars;
	}

	public abstract boolean requiresClosingTag();

	public boolean addChild(CFMLTag tag) {
		if (acceptsChildren(tag) && tag.setParent(this)) {
			children.add(tag);
			return true;
		} else
			return false;
	}

	public boolean removeChild(CFMLTag tag) {
		return children.removeElement(tag);
	}

	public boolean hasParent() {
		return parent != null;
	}

	public boolean hasParent(Class c) {
		CFMLTag tag = this;
		while (tag.hasParent()) {
			tag = tag.parent;
			if (tag.getClass() == c)
				return true;
		}
		return false;
	}

	public Enumeration<CFMLTag> getChildren() {
		return children.elements();
	}

	public boolean validate(IParserHandler parserHandler) {
		Enumeration<CFMLTag> childrenTags = getChildren();
		while (childrenTags.hasMoreElements()) {
			CFMLTag tag = childrenTags.nextElement();
			tag.validate(parserHandler);
		}

		if (startOffset == -1 || attributes == null) {
			if (parserHandler != null)
				parserHandler.addMarker(new ParserException("Malformed tag", ParserException.SEVERITY_ERROR, startLine));
			return false;
		} else {
			return true;
		}
	}

	public String getAttribute(String name) {
		return RegEx.Compile(name + attributePattern, "i").match(attributes).getNext().group(1);
	}

	public boolean acceptsChildren(CFMLTag tag) {
		return parent.acceptsChildren(tag);
	}

	
	// Variables as seen after the tag executes <aa/>#aa.var#
	public Vector<Variable> getVariables(int offset) {
		return new Vector<Variable>();
	}

	// Variables as seen within the tag <aa>#aa.var#</aa>
	public Vector<Variable> getChildVariables(int offset) {
		Vector<Variable> vars = new Vector<Variable>();
		Enumeration<CFMLTag> enumeration = getChildren();

		while (enumeration.hasMoreElements()) {
			CFMLTag tag = enumeration.nextElement();
			if (tag.validate(null))
				vars.addAll(tag.getVariables(offset));

			if (offset >= tag.startOffset && offset < tag.endOffset) {
				vars.addAll(tag.getChildVariables(offset));
			}
		}
		return vars;
	}

	public void closeTag(int offset, int linenumber, IParserHandler parserHandler) {
		setEndOffset(offset);
		setEndLine(linenumber);
	}

	public String getAttributes() {
		return attributes;
	}

	public void setAttributes(String attributes) {
		this.attributes = attributes;
	}

	public int getStartOffset() {
		return startOffset;
	}

	public void setStartOffset(int lineNumber) {
		this.startOffset = lineNumber;
	}

	public CFMLTag getParent() {
		return parent;
	}

	public boolean setParent(CFMLTag parent) {
		this.parent = parent;
		return true;
	}

	public int getEndOffset() {
		return endOffset;
	}

	public void setEndOffset(int endOffset) {
		this.endOffset = endOffset;
	}

	public int getEndLine() {
		return endLine;
	}

	public void setEndLine(int endLine) {
		this.endLine = endLine;
	}

	public int getStartLine() {
		return startLine;
	}

	public void setStartLine(int startLine) {
		this.startLine = startLine;
	}
}
